Programa de Pos graduação em Computação Aplicada – PPCA (UnB)

Nome: Nicola Defonte | Matrıcula: 231108829¶

                                    ### Prova final de AEDI - Questão 2 ###

A questão 2 ira se basear na aplicação pratica de um problema de Ciencia de Dados em Machine Learning. Esses problemas são originados de dados reais aplicados em problemas de Negocios. A questão ira abordar a aplicação do problema Hotel Booking. Esse problema trata-se do objetivo de encontrar um modelo que seja capaz de prever se os indivıduos se cancelam ou não suas reservas em uma rede de hoteis. Com esses dados pede-se:

a) Elaborar uma analise descritiva da base de dados, analise grafica e por tabelas. (10%)
b) Realize a previsão por um modelo de Regressão Logıstica. (60%)
c) Encontre as features mais importantes para o cancelamento das reservas, interprete os resultados. (20%)
d) Explique o motivo do uso da regressão logıstica e não da regressão linear. (10%)

Bibliotecas¶

In [1]:
import statsmodels.api as sm

import pandas as pd
import numpy as np
from sklearn import preprocessing
import matplotlib.pyplot as plt 
from sklearn.linear_model import LogisticRegression
from sklearn.model_selection import train_test_split
import seaborn as sns
import plotly.express as px

sns.set(style="white")
sns.set(style="whitegrid", color_codes=True)

Esplicando as variaveis¶

Hotel: são os hoteis que fezem parte da rede de hoteis.
is_canceled: è uma variavel dicotomica quantitativa para indicar se a reserva foi cancelada (1) ou não (0).
lead_time : è o numero de dias entre a data da reserva e o dia de chegada.
arrival_date_year : è o ano da data da chegada
arrival_date_month : è o mes da data da chegada
arrival_date_week_number : è o numero da semana no ano da data da chegada
arrival_date_day_of_month : numero do dia do mes da data da chegada
stays_in_weekend_nights : numero de dias reservadas no sabado e domingo
stays_in_week_nights : numero de dias reservadas nos dias da semana (seg-sex)
adults : numero de pessoas de maior idade
children : numero de crianças
babies : numero de bebès
meal : tipo de pacote com opçoes de reifeçoes
country : país de origem
market_segment : segmentação de mercado
distribution_channel : canal de distribuição como "TA" agencia de viagem ou "TO" operador turístico.
is_repeated_guest : indica se o usuario ja foi cliente do hotel, sim (1), não (0).
previous_cancellations : numero de cancelamentos de reservas anteriores
previous_bookings_not_canceled : numero de reservas anteriores não canceladas.
reserved_room_type : codigo do tipo de quarto reservado
assigned_room_type : codigo real do quarto atribuído ao usuário
booking_changes : numero de alterações de reserva atè o dia do check-in ou cancelamento
deposit_type : indica se teve pagamento adiantado pela reserva, pode assumir 3 formas:
pagamento total do valor sem rembolso (No_refund);
pagamento parcial do valor total com rembolso (Refundable);
não teve pagamento adiantado (No_deposit);
agent : codigo identificativo do operador turistico que fez a reserva
company : codigo identificativo da impresa que fez a reserva
days_in_waiting_list : numero de dias antes da reserva ser confirmada
customer_type : tipo de cliente
adr : custo medio diario das reservas
required_car_parking_spaces : numero de vaga para carro pedido do cliente
total_of_special_requests : numero de pedidos especiais
reservation_status : è o estato da reserva, pode assumir 3 estados:
check-out: o cliente fez o check-in e foi embora do hotel;
no-show: o cliente não fez o check-in mas informou o hotel da motivação;
canceled: a reserva foi cancelada pelo cliente.
reservation_status_date : data do ultimo estado da reserva, è util se for usado em conjunto com o reservation_status

Condições:¶

Para que a regressão logistica seja realizada è preciso atender as seguintes condiçoes:

  1. A variável dependente deve ser binária;
  2. Independência entre as observações: os valores de uma observação não devem ser influenciados pelos valores de outras observações;
  3. Ausencia de multicolinearidade: as variáveis independentes devem ter pouca ou nenhuma correlação entre si;
  4. Linearidade logística: a relação entre as variáveis independentes e a variável dependente deve ser linear;
  5. Ausencia de outliers;
  6. Amostra suficientemente grande.

Escolha da Regressão Logistica¶

De acordo com a questão da prova, foi pedido para montar um modelo de previsão de cancelamento das reservas, portanto as possibilidade de cancelamento de uma reserva se pode pensar como uma variavel dicotomica, sim ou não, alias 1 ou 0 conforme oferecido no dataset.
A regressão logistica, diferentemente da regressão linear, aceita prever a probabilidade de ocorrência de um evento binário (sim/não) com base em um conjunto de variáveis independentes.
Outras diferenças de condiçoes entre regressão logistica e regressão linear são que essa ultima deve ter condiçoes de normalidade e de homocedasticidade.

Método¶

Foi mostrada uma primeira visualização dos dados e da lista das variaveis.

Os dados nulos e duplicados foram limpos da base de dados.

Em primeira instancia foram descartadas as variaveis que achei poderiam não ser uteis ao objetivo, aquelas baseadas em avaliaçoes intuitivas ou por causa de incompatibilidade de tipo de dados.

Subsequentemente houve uma analise da correlação entre as variaveis por meio da heatmap e tambem foram descartadas algumas variaveis.

As variaveis categoricas e continuas foram subdivididas e organzadas por classes; a variavel y "is_canceled" foi definida como dependente.

Em seguida houve o tratamento, descrição dos dados e visualização. Nessa parte os dados foram organizados em tabelas. As variaveis categoricas foram comparadas cada uma com a variavel dependente. Tambem as variaveis continuas foram mantidas em cada tabela para observaçoes. Em conjunto com cada tabela foram plotados os graficos descritivos das frequencias subdivididas por zero e um; um tratamento especial dos dados foi aplicado as variaveis "country" e "agent" as quais foram selecionadas e plotadas de maneira diferente com o objetivo de selecionar as melhores 10 e criar sub-variaveis dummies

Em seguida foram plotados os histogramas e os boxplot para avaliar as distribuçoes de frequencias e a presença de outliers com analise visual.

Foram criadas mais variaveis dummies a partir das variaveis categoricas selecionadas no começo.

Foi necessario o rebalanceamento dos bancos de dados da variavel dependente porque as porcentagens foram bem maiores num lado do que o outro. O algoritmo SMOTE foi utilizado.

Em seguida as variaveis independentes foram reduzidas por meio do algoritmo RFE obtendo um número parcimonioso de variáveis mais importantes para o modelo.

Foi implementada a Regressão logistica (Logit) com o resultado dos coeficientes para cada variavel em ordem de importancia para o modelo.

Em seguida houve a avaliação da previsibilidade do modelo. A base de dados foi divididas em dados de treino e dados para test em busca da acurácia do modelo com base no teste.

A matriz de confusão foi utilizada para avaliar o numero das predições corretas na base de identificação de verdadeiros positivos, falsos positivos e falsos negativos.

Foram utlizadas as seguintes metricas, Precision, Recall e F1-score para clasificação do modelo. Enfim foi plotada a Curva ROC como metrica visual de avaliação do modelo.

Leitura dos Dados¶

Creando o dataframe do dataset e visualizando as variaveis de cada coluna.

In [2]:
data = pd.read_csv('hotel_bookings.csv', sep = ',', header = 0)

print(data.shape)
print(list(data.columns))
(119390, 32)
['hotel', 'is_canceled', 'lead_time', 'arrival_date_year', 'arrival_date_month', 'arrival_date_week_number', 'arrival_date_day_of_month', 'stays_in_weekend_nights', 'stays_in_week_nights', 'adults', 'children', 'babies', 'meal', 'country', 'market_segment', 'distribution_channel', 'is_repeated_guest', 'previous_cancellations', 'previous_bookings_not_canceled', 'reserved_room_type', 'assigned_room_type', 'booking_changes', 'deposit_type', 'agent', 'company', 'days_in_waiting_list', 'customer_type', 'adr', 'required_car_parking_spaces', 'total_of_special_requests', 'reservation_status', 'reservation_status_date']

Mostrando o dataset

In [3]:
data
Out[3]:
hotel is_canceled lead_time arrival_date_year arrival_date_month arrival_date_week_number arrival_date_day_of_month stays_in_weekend_nights stays_in_week_nights adults ... deposit_type agent company days_in_waiting_list customer_type adr required_car_parking_spaces total_of_special_requests reservation_status reservation_status_date
0 Resort Hotel 0 342 2015 July 27 1 0 0 2 ... No Deposit NaN NaN 0 Transient 0.00 0 0 Check-Out 2015-07-01
1 Resort Hotel 0 737 2015 July 27 1 0 0 2 ... No Deposit NaN NaN 0 Transient 0.00 0 0 Check-Out 2015-07-01
2 Resort Hotel 0 7 2015 July 27 1 0 1 1 ... No Deposit NaN NaN 0 Transient 75.00 0 0 Check-Out 2015-07-02
3 Resort Hotel 0 13 2015 July 27 1 0 1 1 ... No Deposit 304.0 NaN 0 Transient 75.00 0 0 Check-Out 2015-07-02
4 Resort Hotel 0 14 2015 July 27 1 0 2 2 ... No Deposit 240.0 NaN 0 Transient 98.00 0 1 Check-Out 2015-07-03
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
119385 City Hotel 0 23 2017 August 35 30 2 5 2 ... No Deposit 394.0 NaN 0 Transient 96.14 0 0 Check-Out 2017-09-06
119386 City Hotel 0 102 2017 August 35 31 2 5 3 ... No Deposit 9.0 NaN 0 Transient 225.43 0 2 Check-Out 2017-09-07
119387 City Hotel 0 34 2017 August 35 31 2 5 2 ... No Deposit 9.0 NaN 0 Transient 157.71 0 4 Check-Out 2017-09-07
119388 City Hotel 0 109 2017 August 35 31 2 5 2 ... No Deposit 89.0 NaN 0 Transient 104.40 0 0 Check-Out 2017-09-07
119389 City Hotel 0 205 2017 August 35 29 2 7 2 ... No Deposit 9.0 NaN 0 Transient 151.20 0 2 Check-Out 2017-09-07

119390 rows × 32 columns

Procurando linhas duplicadas e mostrandoas na tabela.

In [4]:
data.duplicated().value_counts()
Out[4]:
False    87396
True     31994
Name: count, dtype: int64
In [5]:
data[data.duplicated(keep=False)]
Out[5]:
hotel is_canceled lead_time arrival_date_year arrival_date_month arrival_date_week_number arrival_date_day_of_month stays_in_weekend_nights stays_in_week_nights adults ... deposit_type agent company days_in_waiting_list customer_type adr required_car_parking_spaces total_of_special_requests reservation_status reservation_status_date
4 Resort Hotel 0 14 2015 July 27 1 0 2 2 ... No Deposit 240.0 NaN 0 Transient 98.00 0 1 Check-Out 2015-07-03
5 Resort Hotel 0 14 2015 July 27 1 0 2 2 ... No Deposit 240.0 NaN 0 Transient 98.00 0 1 Check-Out 2015-07-03
21 Resort Hotel 0 72 2015 July 27 1 2 4 2 ... No Deposit 250.0 NaN 0 Transient 84.67 0 1 Check-Out 2015-07-07
22 Resort Hotel 0 72 2015 July 27 1 2 4 2 ... No Deposit 250.0 NaN 0 Transient 84.67 0 1 Check-Out 2015-07-07
39 Resort Hotel 0 70 2015 July 27 2 2 3 2 ... No Deposit 250.0 NaN 0 Transient 137.00 0 1 Check-Out 2015-07-07
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
119352 City Hotel 0 63 2017 August 35 31 0 3 3 ... No Deposit 9.0 NaN 0 Transient-Party 195.33 0 2 Check-Out 2017-09-03
119353 City Hotel 0 63 2017 August 35 31 0 3 3 ... No Deposit 9.0 NaN 0 Transient-Party 195.33 0 2 Check-Out 2017-09-03
119354 City Hotel 0 63 2017 August 35 31 0 3 3 ... No Deposit 9.0 NaN 0 Transient-Party 195.33 0 2 Check-Out 2017-09-03
119372 City Hotel 0 175 2017 August 35 31 1 3 1 ... No Deposit 42.0 NaN 0 Transient 82.35 0 1 Check-Out 2017-09-04
119373 City Hotel 0 175 2017 August 35 31 1 3 1 ... No Deposit 42.0 NaN 0 Transient 82.35 0 1 Check-Out 2017-09-04

40165 rows × 32 columns

Eliminando as linhas duplicadas, 87396 linhas totais sobrando.

In [6]:
data = data.drop_duplicates()
data = data.reset_index(drop=True)
data.shape
Out[6]:
(87396, 32)
In [7]:
#Procurando colunas com valores Nan

pd.set_option('display.max_rows', 500)
pd.set_option('display.max_columns', 500)
pd.set_option('display.width', 1000)

data.isnull().sum(axis = 0)
Out[7]:
hotel                                 0
is_canceled                           0
lead_time                             0
arrival_date_year                     0
arrival_date_month                    0
arrival_date_week_number              0
arrival_date_day_of_month             0
stays_in_weekend_nights               0
stays_in_week_nights                  0
adults                                0
children                              4
babies                                0
meal                                  0
country                             452
market_segment                        0
distribution_channel                  0
is_repeated_guest                     0
previous_cancellations                0
previous_bookings_not_canceled        0
reserved_room_type                    0
assigned_room_type                    0
booking_changes                       0
deposit_type                          0
agent                             12193
company                           82137
days_in_waiting_list                  0
customer_type                         0
adr                                   0
required_car_parking_spaces           0
total_of_special_requests             0
reservation_status                    0
reservation_status_date               0
dtype: int64
In [8]:
# Limpando os dados com Nan

data = data.drop(columns=['company'])
data[['country', 'agent']] = data[['country', 'agent']].fillna('not_defined')
data['children'] = data['children'].fillna(0)
#data.isnull().sum(axis = 0)

Escolhi de tirar do dataset a coluna 'company' pois tinha (82137) quasi a totalidade dos valors 'Nan'; As classes 'country' e 'agent' possuem uns valores aceitaveis de 'Nan' e foram substituidos com o atributo 'not_defined'; Enfim a variavel 'children' tinha so 4 valores "Nan" portanto foram substiduidos com zero.

Mostrando os valores únicos para cada variavel e os de tipo "objeto" pela identificação das variaveis categoricas.

In [9]:
# Convertindo reservation_status_date in datetime type 
data['reservation_status_date'] = pd.to_datetime(data['reservation_status_date'])
In [10]:
data_uni_dty = pd.concat([d.reset_index(drop=False) for d in [data.nunique(), data.dtypes]], axis=1)
data_uni_dty = data_uni_dty.transpose()
data_uni_dty = data_uni_dty.drop_duplicates().transpose()
data_uni_dty.columns = ['variaveis','unique','dtype']
data_uni_dty
Out[10]:
variaveis unique dtype
0 hotel 2 object
1 is_canceled 2 int64
2 lead_time 479 int64
3 arrival_date_year 3 int64
4 arrival_date_month 12 object
5 arrival_date_week_number 53 int64
6 arrival_date_day_of_month 31 int64
7 stays_in_weekend_nights 17 int64
8 stays_in_week_nights 35 int64
9 adults 14 int64
10 children 5 float64
11 babies 5 int64
12 meal 5 object
13 country 178 object
14 market_segment 8 object
15 distribution_channel 5 object
16 is_repeated_guest 2 int64
17 previous_cancellations 15 int64
18 previous_bookings_not_canceled 73 int64
19 reserved_room_type 10 object
20 assigned_room_type 12 object
21 booking_changes 21 int64
22 deposit_type 3 object
23 agent 334 object
24 days_in_waiting_list 128 int64
25 customer_type 4 object
26 adr 8879 float64
27 required_car_parking_spaces 5 int64
28 total_of_special_requests 6 int64
29 reservation_status 3 object
30 reservation_status_date 926 datetime64[ns]

Visualizando os valores descritivos de cada variavel categorica

In [11]:
array_catg = []
for col in data.describe(include=['int64']):
    if col == 'is_canceled' or col == 'is_repeated_guest':
        print(col)
        print(data[col].unique())
        print('-'*60)
for col in data.describe(include=['object']):
        print(col)
        print(data[col].unique())
        print('-'*60)
        array_catg.append(col)
is_canceled
[0 1]
------------------------------------------------------------
is_repeated_guest
[0 1]
------------------------------------------------------------
hotel
['Resort Hotel' 'City Hotel']
------------------------------------------------------------
arrival_date_month
['July' 'August' 'September' 'October' 'November' 'December' 'January'
 'February' 'March' 'April' 'May' 'June']
------------------------------------------------------------
meal
['BB' 'FB' 'HB' 'SC' 'Undefined']
------------------------------------------------------------
country
['PRT' 'GBR' 'USA' 'ESP' 'IRL' 'FRA' 'not_defined' 'ROU' 'NOR' 'OMN' 'ARG'
 'POL' 'DEU' 'BEL' 'CHE' 'CN' 'GRC' 'ITA' 'NLD' 'DNK' 'RUS' 'SWE' 'AUS'
 'EST' 'CZE' 'BRA' 'FIN' 'MOZ' 'BWA' 'LUX' 'SVN' 'ALB' 'IND' 'CHN' 'MEX'
 'MAR' 'UKR' 'SMR' 'LVA' 'PRI' 'SRB' 'CHL' 'AUT' 'BLR' 'LTU' 'TUR' 'ZAF'
 'AGO' 'ISR' 'CYM' 'ZMB' 'CPV' 'ZWE' 'DZA' 'KOR' 'CRI' 'HUN' 'ARE' 'TUN'
 'JAM' 'HRV' 'HKG' 'IRN' 'GEO' 'AND' 'GIB' 'URY' 'JEY' 'CAF' 'CYP' 'COL'
 'GGY' 'KWT' 'NGA' 'MDV' 'VEN' 'SVK' 'FJI' 'KAZ' 'PAK' 'IDN' 'LBN' 'PHL'
 'SEN' 'SYC' 'AZE' 'BHR' 'NZL' 'THA' 'DOM' 'MKD' 'MYS' 'ARM' 'JPN' 'LKA'
 'CUB' 'CMR' 'BIH' 'MUS' 'COM' 'SUR' 'UGA' 'BGR' 'CIV' 'JOR' 'SYR' 'SGP'
 'BDI' 'SAU' 'VNM' 'PLW' 'QAT' 'EGY' 'PER' 'MLT' 'MWI' 'ECU' 'MDG' 'ISL'
 'UZB' 'NPL' 'BHS' 'MAC' 'TGO' 'TWN' 'DJI' 'STP' 'KNA' 'ETH' 'IRQ' 'HND'
 'RWA' 'KHM' 'MCO' 'BGD' 'IMN' 'TJK' 'NIC' 'BEN' 'VGB' 'TZA' 'GAB' 'GHA'
 'TMP' 'GLP' 'KEN' 'LIE' 'GNB' 'MNE' 'UMI' 'MYT' 'FRO' 'MMR' 'PAN' 'BFA'
 'LBY' 'MLI' 'NAM' 'BOL' 'PRY' 'BRB' 'ABW' 'AIA' 'SLV' 'DMA' 'PYF' 'GUY'
 'LCA' 'ATA' 'GTM' 'ASM' 'MRT' 'NCL' 'KIR' 'SDN' 'ATF' 'SLE' 'LAO']
------------------------------------------------------------
market_segment
['Direct' 'Corporate' 'Online TA' 'Offline TA/TO' 'Complementary' 'Groups'
 'Undefined' 'Aviation']
------------------------------------------------------------
distribution_channel
['Direct' 'Corporate' 'TA/TO' 'Undefined' 'GDS']
------------------------------------------------------------
reserved_room_type
['C' 'A' 'D' 'E' 'G' 'F' 'H' 'L' 'P' 'B']
------------------------------------------------------------
assigned_room_type
['C' 'A' 'D' 'E' 'G' 'F' 'I' 'B' 'H' 'P' 'L' 'K']
------------------------------------------------------------
deposit_type
['No Deposit' 'Refundable' 'Non Refund']
------------------------------------------------------------
agent
['not_defined' 304.0 240.0 303.0 15.0 241.0 8.0 250.0 115.0 5.0 175.0
 134.0 156.0 243.0 242.0 3.0 105.0 40.0 147.0 306.0 184.0 96.0 2.0 127.0
 95.0 146.0 9.0 177.0 6.0 143.0 244.0 149.0 167.0 300.0 171.0 305.0 67.0
 196.0 152.0 142.0 261.0 104.0 36.0 26.0 29.0 258.0 110.0 71.0 181.0 88.0
 251.0 275.0 69.0 248.0 208.0 256.0 314.0 126.0 281.0 273.0 253.0 185.0
 330.0 334.0 328.0 326.0 321.0 324.0 313.0 38.0 155.0 68.0 335.0 308.0
 332.0 94.0 348.0 310.0 339.0 375.0 66.0 327.0 387.0 298.0 91.0 245.0
 385.0 257.0 393.0 168.0 405.0 249.0 315.0 75.0 128.0 307.0 11.0 436.0 1.0
 201.0 183.0 223.0 368.0 336.0 291.0 464.0 411.0 481.0 10.0 154.0 468.0
 410.0 390.0 440.0 495.0 492.0 493.0 434.0 57.0 531.0 420.0 483.0 526.0
 472.0 429.0 16.0 446.0 34.0 78.0 139.0 252.0 270.0 47.0 114.0 301.0 193.0
 182.0 135.0 350.0 195.0 352.0 355.0 159.0 363.0 384.0 360.0 331.0 367.0
 64.0 406.0 163.0 414.0 333.0 427.0 431.0 430.0 426.0 438.0 433.0 418.0
 441.0 282.0 432.0 72.0 450.0 180.0 454.0 455.0 59.0 451.0 254.0 358.0
 469.0 165.0 467.0 510.0 337.0 476.0 502.0 527.0 479.0 508.0 535.0 302.0
 497.0 187.0 13.0 7.0 27.0 14.0 22.0 17.0 28.0 42.0 20.0 19.0 45.0 37.0
 61.0 39.0 21.0 24.0 41.0 50.0 30.0 54.0 52.0 12.0 44.0 31.0 83.0 32.0
 63.0 60.0 55.0 56.0 89.0 87.0 118.0 86.0 85.0 210.0 214.0 129.0 179.0
 138.0 174.0 170.0 153.0 93.0 151.0 119.0 35.0 173.0 58.0 53.0 133.0 79.0
 235.0 192.0 191.0 236.0 162.0 215.0 157.0 287.0 132.0 234.0 98.0 77.0
 103.0 107.0 262.0 220.0 121.0 205.0 378.0 23.0 296.0 290.0 229.0 33.0
 286.0 276.0 425.0 484.0 323.0 403.0 219.0 394.0 509.0 111.0 423.0 4.0
 70.0 82.0 81.0 74.0 92.0 99.0 90.0 112.0 117.0 106.0 148.0 158.0 144.0
 211.0 213.0 216.0 232.0 150.0 267.0 227.0 247.0 278.0 280.0 285.0 289.0
 269.0 295.0 265.0 288.0 122.0 294.0 325.0 341.0 344.0 346.0 359.0 283.0
 364.0 370.0 371.0 25.0 141.0 391.0 397.0 416.0 404.0 299.0 197.0 73.0
 354.0 444.0 408.0 461.0 388.0 453.0 459.0 474.0 475.0 480.0 449.0]
------------------------------------------------------------
customer_type
['Transient' 'Contract' 'Transient-Party' 'Group']
------------------------------------------------------------
reservation_status
['Check-Out' 'Canceled' 'No-Show']
------------------------------------------------------------

Visualizando as variaveis continuas e colocando numa lista

In [12]:
cont_vars = []
for col in data.describe(include=['int64','float64','datetime64[ns]']):
    if col == 'is_canceled' or col == 'is_repeated_guest':
        continue
    cont_vars.append(col)
cont_vars
Out[12]:
['lead_time',
 'arrival_date_year',
 'arrival_date_week_number',
 'arrival_date_day_of_month',
 'stays_in_weekend_nights',
 'stays_in_week_nights',
 'adults',
 'children',
 'babies',
 'previous_cancellations',
 'previous_bookings_not_canceled',
 'booking_changes',
 'days_in_waiting_list',
 'adr',
 'required_car_parking_spaces',
 'total_of_special_requests',
 'reservation_status_date']

Procurando correlação entre as variaveis

In [13]:
#Heatmap with the correlation values
heat_data = data.copy()
heat_data = heat_data.filter(cont_vars + ['is_canceled'] + ['is_repeated_guest']) 
plt.figure(figsize = (24, 12))
sns.heatmap(heat_data.corr(), annot=True)
Out[13]:
<matplotlib.axes._subplots.AxesSubplot at 0x12c06da90>

Observando a heatmap è possivel observar que tem forte correlação entre as seguintes variaveis: "reservation_status_date" e "arrival_date_year".
Uma provavel correlação entre "is_repeated_guest" e "previous_bookings_not_canceled" pode fazer sentido.
Tambem entre "stays_in_weekend_nights" and "stays_in_week_nights" è provavel.

In [14]:
#Selecionando as variaveis continuas 
to_remove = ["reservation_status_date", 'arrival_date_year','previous_bookings_not_canceled',
             'stays_in_weekend_nights','stays_in_week_nights','arrival_date_week_number','arrival_date_day_of_month',]
sel_cont_vars = list(set(cont_vars) - set(to_remove))
sel_cont_vars
Out[14]:
['total_of_special_requests',
 'adr',
 'children',
 'required_car_parking_spaces',
 'days_in_waiting_list',
 'lead_time',
 'babies',
 'adults',
 'booking_changes',
 'previous_cancellations']

As variaveis, 'arrival_date_week_number','arrival_date_day_of_month' foram eliminadas pois não tem influencia no alcançamento do modelo.

Tratamento e Descrição dos Dados¶

Definendo 'is_canceled' como variavel dependente.
Procurando quantos cancelamentos foram feitos em porcentagem.

In [15]:
#Definendo variavel y 'is_canceled'
def_y_var = 'is_canceled'
In [16]:
data[def_y_var].value_counts()
Out[16]:
is_canceled
0    63371
1    24025
Name: count, dtype: int64
In [17]:
figure = sns.countplot(x = def_y_var, data = data)
#plt.figure(figsize=(15,8))
plt.title("numero de cancelamentos")
plt.show()
In [18]:
res_no_canc = (data[def_y_var]==0).sum()
res_canc = (data[def_y_var]==1).sum()
pct_of_no_canc = res_no_canc/(res_no_canc+res_canc)
print("Percentual de pessoas que não cancelaram", round(pct_of_no_canc*100, 2), '%')
pct_of_canc = res_canc/(res_no_canc+res_canc)
print("Percentual de pessoas que cancelaram", round(pct_of_canc*100, 2), '%')
Percentual de pessoas que não cancelaram 72.51 %
Percentual de pessoas que cancelaram 27.49 %

Observando as quantidades de cancelamentos por cadaum hotel.

In [19]:
data['hotel'].unique()
Out[19]:
array(['Resort Hotel', 'City Hotel'], dtype=object)
In [20]:
data_hotel = data.copy()
data_hotel = data_hotel.filter(['hotel'] + [def_y_var] + sel_cont_vars) 
data_hotel.groupby('hotel').mean()
Out[20]:
is_canceled total_of_special_requests adr children required_car_parking_spaces days_in_waiting_list lead_time babies adults booking_changes previous_cancellations
hotel
City Hotel 0.300386 0.710994 110.985944 0.131841 0.035618 1.020233 77.678521 0.007337 1.876338 0.246369 0.035768
Resort Hotel 0.234809 0.679021 99.025346 0.149317 0.160681 0.323834 83.371938 0.016309 1.874941 0.311293 0.021991
In [21]:
sns.countplot(x = 'hotel',hue = 'is_canceled', data = data)
plt.title("cancelamentos entres os hoteis")
plt.show()

Colocando a variavel de cancelamento 'is_canceled' em função das variaveis continuas selecionadas.

In [22]:
data_can = data.copy()
data_can = data_can.filter(['is_canceled'] + sel_cont_vars) 
data_can.groupby('is_canceled').mean()
Out[22]:
total_of_special_requests adr children required_car_parking_spaces days_in_waiting_list lead_time babies adults booking_changes previous_cancellations
is_canceled
0 0.760316 102.001961 0.119724 0.116157 0.722034 70.099588 0.012261 1.844235 0.313535 0.018715
1 0.535692 117.772476 0.188512 0.000000 0.822185 105.719251 0.007034 1.959043 0.160999 0.061270

Observando a variavel is_canceled e as variaveis cotinuas em função da variavel is_repeated_guest

In [23]:
data_hotel = data.copy()
data_hotel = data_hotel.filter(['is_repeated_guest'] + [def_y_var] + sel_cont_vars) 
data_hotel.groupby('is_repeated_guest').mean()
Out[23]:
is_canceled total_of_special_requests adr children required_car_parking_spaces days_in_waiting_list lead_time babies adults booking_changes previous_cancellations
is_repeated_guest
0 0.282969 0.698789 108.035027 0.142830 0.080137 0.775449 82.442255 0.011122 1.897489 0.270716 0.015051
1 0.076428 0.693119 64.585769 0.035432 0.184773 0.113031 17.160469 0.003514 1.342313 0.293411 0.408199

Plot da variavel is_canceled em função da variavel is_repeated_guest

In [24]:
sns.countplot(x = 'is_repeated_guest',hue = 'is_canceled', data = data)
plt.title("cancelamentos entres repeated guests")
plt.show()

Observando a variavel is_canceled e as variaveis cotinuas em função da variavel arrival_date_month

In [25]:
data_mon = data.copy()
data_mon = data_mon.filter(['arrival_date_month'] + [def_y_var] + sel_cont_vars) 
data_mon.groupby('arrival_date_month').mean()
Out[25]:
is_canceled total_of_special_requests adr children required_car_parking_spaces days_in_waiting_list lead_time babies adults booking_changes previous_cancellations
arrival_date_month
April 0.304628 0.654148 103.612589 0.140238 0.079287 1.148710 75.855336 0.006702 1.884294 0.275923 0.017704
August 0.321844 0.835835 150.876120 0.243493 0.087679 0.195167 102.679133 0.017411 2.017234 0.297415 0.016967
December 0.268564 0.732021 81.450226 0.140324 0.096083 1.014812 58.997077 0.015202 1.845059 0.282206 0.052621
February 0.232043 0.638570 74.692033 0.126435 0.082158 0.197114 35.126927 0.009675 1.806658 0.237127 0.027222
January 0.221180 0.636054 70.050742 0.094183 0.103345 0.940337 34.463243 0.010228 1.728319 0.287236 0.082250
July 0.317987 0.767625 135.542014 0.227603 0.086010 0.098538 111.960127 0.011932 2.009844 0.251069 0.033310
June 0.303155 0.688345 119.750120 0.131616 0.078429 0.496845 103.982743 0.009015 1.898519 0.252672 0.018158
March 0.243578 0.573939 81.609523 0.090643 0.078264 0.390523 53.409024 0.007587 1.810196 0.254625 0.014242
May 0.292280 0.665230 111.195703 0.098983 0.072172 1.261041 92.169240 0.008618 1.850389 0.277080 0.015320
November 0.211011 0.707508 72.754460 0.052853 0.088889 0.647447 47.582983 0.008809 1.703904 0.298498 0.033634
October 0.236804 0.678685 90.152518 0.096337 0.088693 1.698731 82.922123 0.008941 1.824776 0.274301 0.041246
September 0.245441 0.696562 112.081263 0.086996 0.081315 1.505830 94.230942 0.013004 1.876532 0.277578 0.050822

Plot da variavel is_canceled em função da variavel arrival_date_month

In [26]:
fig, ax = plt.subplots(figsize=(11, 4))
sns.countplot(x = 'arrival_date_month',hue = 'is_canceled', data = data)
plt.title("cancelamentos por mes de chegada")
plt.show()

Mostrando os 10 paises com mais reservas¶

In [27]:
data['country'].value_counts()[:10].reset_index()
Out[27]:
country count
0 PRT 27453
1 GBR 10433
2 FRA 8837
3 ESP 7252
4 DEU 5387
5 ITA 3066
6 IRL 3016
7 BEL 2081
8 BRA 1995
9 NLD 1911

Paises com mais frequencias de cancelamentos

In [28]:
canceled_data = data[data['is_canceled'] == 1]
top_10_country = canceled_data['country'].value_counts()[:10].reset_index()
top_10_country.columns = ['country','dados']
print(top_10_country)
fig = px.pie(top_10_country,names='country',values='dados',title='Top 10 paises com mais frequencias de cancelamentos',template='simple_white')
fig.update_traces(textposition='inside',textinfo='label+value+percent')
fig.show()
  country  dados
0     PRT   9791
1     GBR   1985
2     ESP   1862
3     FRA   1733
4     ITA   1075
5     DEU   1053
6     BRA    727
7     IRL    668
8     USA    459
9     BEL    411

Selecionando os top 10 paises com maior numero de reservas e com mais cancelamentos em porcentagem.

In [29]:
data_cou = data.copy()
data_cou = data_cou.filter(['country'] + [def_y_var] + sel_cont_vars) 
data_cou['counter'] = data_cou.groupby('country')['country'].transform('count')
In [30]:
data_cou.groupby('country').mean()
data_cou2 = data_cou.iloc[:,[0,len(sel_cont_vars)+2,*range(1,len(sel_cont_vars)+1)]]
data_cou2 = data_cou2.groupby('country').mean()
sorted_data_cou = data_cou2.sort_values(by=['counter'], ascending=False).head(10)
sorted_data_cou2 = sorted_data_cou.sort_values(by=['is_canceled'], ascending=False).head(10)
#sorted_data_cou2 = sorted_data_cou2.where(sorted_data_cou2['counter'] > 10, np.nan)
#sorted_data_cou2 = sorted_data_cou2.dropna()
print(sorted_data_cou2.shape)
sorted_data_cou2
#sorted_data_cou2.plot(x='country', y='counter')
(10, 11)
Out[30]:
counter is_canceled total_of_special_requests adr children required_car_parking_spaces days_in_waiting_list lead_time babies adults booking_changes
country
BRA 1995.0 0.364411 0.853634 112.726937 0.189474 0.069674 0.204010 80.864662 0.012030 2.003008 0.279699
PRT 27453.0 0.356646 0.594252 95.839512 0.124176 0.111318 1.279678 65.101228 0.015845 1.759371 0.268167
ITA 3066.0 0.350620 0.691455 116.064018 0.145140 0.041422 0.961840 83.231246 0.005219 1.970646 0.278865
ESP 7252.0 0.256757 0.769029 122.283610 0.190017 0.162300 0.149890 52.196773 0.017375 1.954909 0.267788
IRL 3016.0 0.221485 0.810676 100.523140 0.101790 0.027851 0.043103 114.276857 0.007958 1.957560 0.266247
BEL 2081.0 0.197501 0.865449 115.379188 0.144642 0.063912 0.119173 94.290245 0.005766 1.962037 0.233061
FRA 8837.0 0.196107 0.765984 112.531958 0.134661 0.064049 0.846667 74.135906 0.008713 1.967636 0.236958
DEU 5387.0 0.195471 0.696120 105.939399 0.087990 0.045109 1.051977 105.089103 0.003341 1.915166 0.230555
GBR 10433.0 0.190262 0.700182 97.669062 0.115978 0.052621 0.377264 117.419055 0.008818 1.924950 0.293013
NLD 1911.0 0.183150 0.796442 109.204359 0.145474 0.094715 0.358974 79.920984 0.005233 1.893250 0.299843

O Portugal apesar de não ser clasificado como primeiro em relação aos numero de cancelamento è o maior pais em termos de frequecias. O Brasil è primeiro em termos de porcentagem de cancelamentos. A Hollanda entra entre os primeiros 10 paises em vez do que os EUA.

In [31]:
sorted_data_cou2_plot = sorted_data_cou2.reset_index()
fig = px.histogram(sorted_data_cou2_plot, x="country", y="is_canceled", title='Top 10 paises com mais cancelamentos em porcentagem')
fig.show()

Criando as variaveis dummies para os top10 paises em porcentagem de cancelamento

In [32]:
first_countries = []
for row in sorted_data_cou2.index:
    first_countries.append('country_'+row)

data_cou = data.copy()
cat_vars_cou =['country']
for var in cat_vars_cou:
    cat_list='var'+'_'+var
    cat_list = pd.get_dummies(data_cou[var], prefix=var, dtype=int)
    data1=data_cou.join(cat_list)
    data_cou=data1

filtered_data_cou = data_cou.filter(first_countries)
filtered_data_cou
Out[32]:
country_BRA country_PRT country_ITA country_ESP country_IRL country_BEL country_FRA country_DEU country_GBR country_NLD
0 0 1 0 0 0 0 0 0 0 0
1 0 1 0 0 0 0 0 0 0 0
2 0 0 0 0 0 0 0 0 1 0
3 0 0 0 0 0 0 0 0 1 0
4 0 0 0 0 0 0 0 0 1 0
... ... ... ... ... ... ... ... ... ... ...
87391 0 0 0 0 0 1 0 0 0 0
87392 0 0 0 0 0 0 1 0 0 0
87393 0 0 0 0 0 0 0 1 0 0
87394 0 0 0 0 0 0 0 0 1 0
87395 0 0 0 0 0 0 0 1 0 0

87396 rows × 10 columns

Separando os dados de cancelamento para cada pais selecionado

In [33]:
filtered_data_cou_chart = pd.concat([data['is_canceled'], filtered_data_cou], axis=1)
filtered_data_cou_chart = filtered_data_cou_chart.groupby('is_canceled').sum()
#filtered_data_cou_chart = filtered_data_cou_chart.reset_index()
filtered_data_cou_chart
Out[33]:
country_BRA country_PRT country_ITA country_ESP country_IRL country_BEL country_FRA country_DEU country_GBR country_NLD
is_canceled
0 1268 17662 1991 5390 2348 1670 7104 4334 8448 1561
1 727 9791 1075 1862 668 411 1733 1053 1985 350

Observando a variavel is_canceled e as variaveis cotinuas em função da variavel meal

In [34]:
data_mea = data.copy()
data_mea = data_mea.filter(['meal'] + [def_y_var] + sel_cont_vars)
data_mea1 = data_mea.groupby('meal').mean()
data_mea1.sort_values(by=['is_canceled'], ascending=False)
Out[34]:
is_canceled total_of_special_requests adr children required_car_parking_spaces days_in_waiting_list lead_time babies adults booking_changes previous_cancellations
meal
SC 0.353022 0.755406 98.278183 0.018880 0.024892 0.036389 66.255247 0.003903 1.849699 0.184158 0.011602
FB 0.275000 0.505556 143.293667 0.213889 0.152778 0.091667 97.580556 0.066667 1.994444 0.636111 0.241667
HB 0.269675 0.708971 133.272635 0.189323 0.106769 1.701266 114.984920 0.019923 1.970941 0.427958 0.022675
BB 0.265483 0.692709 103.669533 0.148342 0.089088 0.693680 76.942790 0.010136 1.866810 0.258422 0.033011
Undefined 0.166667 0.361789 105.811850 0.113821 0.089431 5.121951 89.097561 0.030488 1.776423 0.623984 0.022358

Plot da variavel is_canceled em função da variavel meal

In [35]:
fig, ax = plt.subplots(figsize=(11, 4))
sns.countplot(x = 'meal',hue = 'is_canceled', data = data)
plt.title("cancelamentos por mes de chegada")
plt.show()

Observando a variavel is_canceled e as variaveis cotinuas em função da variavel market_segment

In [36]:
data_mseg = data.copy()
data_mseg = data_mseg.filter(['market_segment'] + [def_y_var] + sel_cont_vars) 
data_mseg['counter'] = data_mseg.groupby('market_segment')['market_segment'].transform('count')

data_mseg.groupby('market_segment').mean()
data_mseg2 = data_mseg.iloc[:,[0,len(sel_cont_vars)+2,*range(1,len(sel_cont_vars)+1)]]
data_mseg2 = data_mseg2.groupby('market_segment').mean()
sorted_data_mseg = data_mseg2.sort_values(by=['counter'], ascending=False)
sorted_data_mseg2 = sorted_data_mseg.sort_values(by=['is_canceled'], ascending=False)
sorted_data_mseg2
Out[36]:
counter is_canceled total_of_special_requests adr children required_car_parking_spaces days_in_waiting_list lead_time babies adults booking_changes
market_segment
Undefined 2.0 1.000000 1.500000 15.000000 0.000000 0.000000 0.000000 1.500000 0.000000 2.500000 0.000000
Online TA 51618.0 0.353462 0.903832 118.171606 0.176063 0.073521 0.002499 79.902805 0.008330 1.961196 0.208183
Groups 4942.0 0.270134 0.163092 74.864284 0.011736 0.052206 6.803116 147.605423 0.002631 1.656212 0.636180
Aviation 227.0 0.198238 0.118943 100.170396 0.000000 0.026432 0.000000 4.281938 0.000000 1.008811 0.255507
Offline TA/TO 13889.0 0.148535 0.351933 81.764191 0.046008 0.039456 2.208222 106.052416 0.012672 1.865793 0.230542
Direct 11804.0 0.147154 0.580312 116.579429 0.187987 0.177652 0.043290 48.825059 0.024483 1.883514 0.410200
Complementary 702.0 0.125356 0.958689 3.049245 0.086895 0.115385 0.055556 13.635328 0.031339 1.504274 0.336182
Corporate 4212.0 0.121083 0.273267 68.151246 0.012108 0.136752 0.127968 16.252849 0.003799 1.206553 0.358262

Plot da variavel is_canceled em função da variavel market_segment

In [37]:
fig, ax = plt.subplots(figsize=(11, 4))
sns.countplot(x = 'market_segment',hue = 'is_canceled', data = data)
plt.title("cancelamentos por mes de chegada")
plt.show()

Observando a variavel is_canceled e as variaveis cotinuas em função da variavel distribution_channel

In [38]:
data_dch = data.copy()
data_dch = data_dch.filter(['distribution_channel'] + [def_y_var] + sel_cont_vars) 
data_dch['counter'] = data_dch.groupby('distribution_channel')['distribution_channel'].transform('count')

data_dch.groupby('distribution_channel').mean()
data_dch2 = data_dch.iloc[:,[0,len(sel_cont_vars)+2,*range(1,len(sel_cont_vars)+1)]]
data_dch2 = data_dch2.groupby('distribution_channel').mean()
sorted_data_dch = data_dch2.sort_values(by=['counter'], ascending=False)
sorted_data_dch2 = sorted_data_dch.sort_values(by=['is_canceled'], ascending=False)
sorted_data_dch2
Out[38]:
counter is_canceled total_of_special_requests adr children required_car_parking_spaces days_in_waiting_list lead_time babies adults booking_changes
distribution_channel
Undefined 5.0 0.800000 1.400000 46.240000 0.200000 0.200000 0.000000 23.000000 0.000000 2.200000 0.000000
TA/TO 69141.0 0.309686 0.756845 108.559116 0.141493 0.064564 0.863930 88.638478 0.008866 1.927409 0.231455
GDS 181.0 0.198895 0.204420 120.317845 0.000000 0.000000 0.000000 20.121547 0.000000 1.088398 0.099448
Direct 12988.0 0.148214 0.568140 109.133604 0.175162 0.170927 0.368263 52.362796 0.024022 1.853172 0.426548
Corporate 5081.0 0.127534 0.255855 68.515682 0.011218 0.133045 0.195434 33.416257 0.004133 1.259004 0.428262

Plot da variavel is_canceled em função da variavel distribution_channel

In [39]:
fig, ax = plt.subplots(figsize=(11, 4))
sns.countplot(x = 'distribution_channel',hue = 'is_canceled', data = data)
plt.title("cancelamentos por mes de chegada")
plt.show()

Observando a variavel is_canceled e as variaveis cotinuas em função da variavel reserved_room_type

In [40]:
data_rrt = data.copy()
data_rrt = data_rrt.filter(['reserved_room_type'] + [def_y_var] + sel_cont_vars)
data_rrt.groupby('reserved_room_type').mean()
Out[40]:
is_canceled total_of_special_requests adr children required_car_parking_spaces days_in_waiting_list lead_time babies adults booking_changes previous_cancellations
reserved_room_type
A 0.259708 0.650286 92.297814 0.050237 0.068839 1.064206 78.111278 0.008541 1.774933 0.266852 0.038460
B 0.318318 0.794795 90.377878 0.607608 0.043043 0.139139 113.345345 0.014014 1.527528 0.561562 0.036036
C 0.323497 0.784699 160.561770 1.288525 0.193443 0.156284 78.761749 0.072131 2.031694 0.495082 0.008743
D 0.300954 0.798770 122.077545 0.043798 0.074089 0.169100 82.902518 0.010346 2.105932 0.227612 0.013852
E 0.272607 0.860307 125.936130 0.098033 0.165647 0.322367 88.297074 0.013721 1.985617 0.299223 0.018020
F 0.301452 0.737159 168.272310 1.038257 0.134254 0.000000 68.243712 0.016295 1.993978 0.342898 0.012398
G 0.359162 0.656920 176.727904 1.276803 0.199805 0.074074 79.838207 0.031676 2.078947 0.340156 0.024366
H 0.407718 0.394295 188.763993 0.978188 0.281879 0.000000 78.206376 0.015101 2.714765 0.328859 0.005034
L 0.333333 0.000000 124.666667 0.000000 0.000000 0.000000 0.000000 0.000000 2.166667 0.000000 0.166667
P 1.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000

Plot da variavel is_canceled em função da variavel reserved_room_type

In [41]:
fig, ax = plt.subplots(figsize=(11, 4))
sns.countplot(x = 'reserved_room_type',hue = 'is_canceled', data = data)
plt.title("cancelamentos por mes de chegada")
plt.show()

Observando a variavel is_canceled e as variaveis cotinuas em função da variavel assigned_room_type

In [42]:
data_art = data.copy()
data_art = data_art.filter(['assigned_room_type'] + [def_y_var] + sel_cont_vars)
data_art.groupby('assigned_room_type').mean()
Out[42]:
is_canceled total_of_special_requests adr children required_car_parking_spaces days_in_waiting_list lead_time babies adults booking_changes previous_cancellations
assigned_room_type
A 0.306221 0.663960 95.849738 0.045862 0.056248 0.980977 83.338523 0.007233 1.785525 0.238637 0.042213
B 0.219231 0.681319 95.368533 0.323626 0.056593 2.519780 91.806593 0.015934 1.633516 0.495604 0.026923
C 0.182448 0.688684 116.900878 0.573210 0.166744 0.692379 82.182448 0.055427 1.972748 0.520092 0.008776
D 0.242109 0.738766 109.067211 0.054297 0.084388 0.372013 73.857525 0.009763 2.004012 0.243313 0.015424
E 0.235997 0.812370 119.199101 0.087978 0.150382 0.423767 83.172620 0.013482 1.951216 0.299792 0.021265
F 0.246485 0.754067 152.870074 0.793218 0.148883 0.337745 67.588089 0.014888 1.963055 0.344913 0.019024
G 0.301441 0.684548 167.424868 1.111289 0.201761 0.311849 75.607686 0.030424 2.041233 0.361890 0.022418
H 0.352691 0.439093 171.881530 0.837110 0.271955 0.000000 74.810198 0.018414 2.567989 0.338527 0.009915
I 0.014006 0.605042 40.801933 0.151261 0.190476 0.890756 66.733894 0.005602 1.801120 0.910364 0.002801
K 0.043478 0.663043 53.829674 0.047101 0.047101 1.000000 42.271739 0.003623 1.195652 1.170290 0.007246
L 1.000000 0.000000 8.000000 0.000000 0.000000 0.000000 0.000000 0.000000 2.000000 0.000000 1.000000
P 1.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000 0.000000

Plot da variavel is_canceled em função da variavel assigned_room_type

In [43]:
fig, ax = plt.subplots(figsize=(11, 4))
sns.countplot(x = 'assigned_room_type',hue = 'is_canceled', data = data)
plt.title("cancelamentos por assigned_room_type")
plt.show()

Observando a variavel is_canceled e as variaveis cotinuas em função da variavel deposit_type

In [44]:
data_dep = data.copy()
data_dep = data_dep.filter(['deposit_type'] + [def_y_var] + sel_cont_vars) 
data_dep['counter'] = data_dep.groupby('deposit_type')['deposit_type'].transform('count')
data_dep.groupby('deposit_type').mean()
data_dep2 = data_dep.iloc[:,[0,len(sel_cont_vars)+2,*range(1,len(sel_cont_vars)+1)]]
data_dep2.groupby('deposit_type').mean()
Out[44]:
counter is_canceled total_of_special_requests adr children required_car_parking_spaces days_in_waiting_list lead_time babies adults booking_changes
deposit_type
No Deposit 86251.0 0.266849 0.707400 106.537882 0.140311 0.085100 0.617732 78.228600 0.010968 1.877764 0.273249
Non Refund 1038.0 0.947013 0.016378 92.388073 0.008671 0.000963 11.007707 211.304432 0.000000 1.712909 0.085742
Refundable 107.0 0.242991 0.196262 79.928318 0.046729 0.186916 7.504673 145.392523 0.000000 1.869159 0.747664

Plot da variavel is_canceled em função da variavel deposit_type

In [45]:
fig, ax = plt.subplots(figsize=(11, 4))
sns.countplot(x = 'deposit_type',hue = 'is_canceled', data = data)
plt.title("cancelamentos por deposit_type")
plt.show()

Selecionando os primeiros 10 agentes¶

In [46]:
data['agent'].value_counts()[:10].reset_index()
Out[46]:
agent count
0 9.0 28759
1 240.0 13028
2 not_defined 12193
3 14.0 3349
4 7.0 3300
5 250.0 2779
6 241.0 1644
7 28.0 1502
8 8.0 1383
9 1.0 1232

Agentes com mais frequencias de cancelamentos

In [47]:
canceled_data = data[data['is_canceled'] == 1]
top_10_agent = canceled_data['agent'].value_counts()[:10].reset_index()
top_10_agent.columns = ['agent','dados']
print(top_10_agent)
fig = px.pie(top_10_agent,names='agent',values='dados',title='Top 10 agentes com mais frequencias de cancelamentos',template='simple_white')
fig.update_traces(textposition='inside',textinfo='label+value+percent')
fig.show()
         agent  dados
0          9.0  11524
1        240.0   4944
2  not_defined   1557
3         14.0    583
4        250.0    494
5          1.0    458
6          7.0    436
7          8.0    385
8        242.0    236
9        241.0    218

Selecionando os top 10 agentes com maior numero de reservas e com mais cancelamentos em porcentagem.

In [48]:
data_agn = data.copy()
data_agn = data_agn.filter(['agent'] + [def_y_var] + sel_cont_vars) 
data_agn['counter'] = data_agn.groupby('agent')['agent'].transform('count')

data_agn.groupby('agent').mean()
data_agn2 = data_agn.iloc[:,[0,len(sel_cont_vars)+2,*range(1,len(sel_cont_vars)+1)]]
data_agn2 = data_agn2.groupby('agent').mean()
sorted_data_agn = data_agn2.sort_values(by=['counter'], ascending=False).head(10)
sorted_data_agn2 = sorted_data_agn.sort_values(by=['is_canceled'], ascending=False).head(10)
print(sorted_data_agn2.shape)
sorted_data_agn2
(10, 11)
Out[48]:
counter is_canceled total_of_special_requests adr children required_car_parking_spaces days_in_waiting_list lead_time babies adults booking_changes
agent
9.0 28759.0 0.400709 0.910602 123.540350 0.161132 0.037310 0.000000 81.949963 0.005459 2.000661 0.209708
240.0 13028.0 0.379490 0.990636 117.514454 0.222597 0.165029 0.000000 82.682683 0.015121 1.940897 0.235263
1.0 1232.0 0.371753 0.148539 75.048109 0.008929 0.001623 10.518669 148.228084 0.001623 1.686688 0.333604
8.0 1383.0 0.278380 0.791757 111.346074 0.151121 0.038322 0.064353 86.064353 0.005061 1.868402 0.109906
250.0 2779.0 0.177762 0.639798 133.727280 0.253329 0.274559 0.000000 70.381792 0.037064 1.991364 0.546240
14.0 3349.0 0.174082 0.577486 127.307961 0.223649 0.085399 0.002986 67.349358 0.018812 1.888922 0.373544
241.0 1644.0 0.132603 1.055961 101.855426 0.181265 0.147810 0.000000 70.793187 0.014599 1.967762 0.122263
7.0 3300.0 0.132121 0.936364 96.659303 0.151212 0.006970 0.000000 68.678485 0.004242 1.873939 0.102121
not_defined 12193.0 0.127696 0.447306 82.984567 0.076273 0.157303 0.383909 37.045436 0.012876 1.579267 0.416632
28.0 1502.0 0.057923 0.225033 79.714281 0.045273 0.003329 0.000666 58.679095 0.011984 2.006658 0.076565

Na lista dos top 10 agentes o agente com id 28 entrou nos primeiros 10 agentes no lugar do agente com id 242. Essa foi a unica mudança.

In [49]:
#Plotando as porcentagem de cancelamento para cada agente
sorted_data_agn2_plot = sorted_data_agn2.reset_index()
sorted_data_agn2_plot['agent'] = sorted_data_agn2_plot['agent'].astype("string")
fig = px.histogram(sorted_data_agn2_plot, x="agent", y="is_canceled", title='Top 10 agentes com mais cancelamentos em porcentagem')
fig.show()

Criando as variaveis dummies

In [50]:
first_agents = []
for row in sorted_data_agn2.index:
    first_agents.append('agent_'+ str(row))

data_agn = data.copy()
cat_vars_cou =['agent']
for var in cat_vars_cou:
    cat_list='var'+'_'+var
    cat_list = pd.get_dummies(data_cou[var], prefix=var, dtype=int)
    data1=data_agn.join(cat_list)
    data_agn=data1

filtered_data_agn = data_agn.filter(first_agents)
filtered_data_agn
Out[50]:
agent_9.0 agent_240.0 agent_1.0 agent_8.0 agent_250.0 agent_14.0 agent_241.0 agent_7.0 agent_not_defined agent_28.0
0 0 0 0 0 0 0 0 0 1 0
1 0 0 0 0 0 0 0 0 1 0
2 0 0 0 0 0 0 0 0 1 0
3 0 0 0 0 0 0 0 0 0 0
4 0 1 0 0 0 0 0 0 0 0
... ... ... ... ... ... ... ... ... ... ...
87391 0 0 0 0 0 0 0 0 0 0
87392 1 0 0 0 0 0 0 0 0 0
87393 1 0 0 0 0 0 0 0 0 0
87394 0 0 0 0 0 0 0 0 0 0
87395 1 0 0 0 0 0 0 0 0 0

87396 rows × 10 columns

Separando os dados de cancelamento para cada agente selecionado

In [51]:
filtered_data_agn_chart = pd.concat([data['is_canceled'], filtered_data_agn], axis=1)
filtered_data_agn_chart.shape
filtered_data_agn_chart = filtered_data_agn_chart.groupby('is_canceled').sum()
#filtered_data_agn_chart = filtered_data_agn_chart.reset_index()
filtered_data_agn_chart
Out[51]:
agent_9.0 agent_240.0 agent_1.0 agent_8.0 agent_250.0 agent_14.0 agent_241.0 agent_7.0 agent_not_defined agent_28.0
is_canceled
0 17235 8084 774 998 2285 2766 1426 2864 10636 1415
1 11524 4944 458 385 494 583 218 436 1557 87

Observando a variavel is_canceled e as variaveis cotinuas em função da variavel customer_type

In [52]:
data_cus = data.copy()
data_cus = data_cus.filter(['customer_type'] + [def_y_var] + sel_cont_vars) 
data_cus['counter'] = data_cus.groupby('customer_type')['customer_type'].transform('count')

data_cus.groupby('customer_type').mean()
data_cus2 = data_cus.iloc[:,[0,len(sel_cont_vars)+2,*range(1,len(sel_cont_vars)+1)]]
data_cus2 = data_cus2.groupby('customer_type').mean()
sorted_data_cus = data_cus2.sort_values(by=['counter'], ascending=False)
sorted_data_cus2 = sorted_data_cus.sort_values(by=['is_canceled'], ascending=False)
print(sorted_data_cus2.shape)
sorted_data_cus2
(4, 11)
Out[52]:
counter is_canceled total_of_special_requests adr children required_car_parking_spaces days_in_waiting_list lead_time babies adults booking_changes
customer_type
Transient 71986.0 0.301059 0.732073 110.135859 0.153669 0.088739 0.177896 73.427236 0.011377 1.903398 0.224238
Contract 3139.0 0.163109 0.838484 92.753036 0.083785 0.042052 0.019433 109.225231 0.010194 1.909525 0.150048
Transient-Party 11727.0 0.152383 0.457918 87.675056 0.064211 0.067366 4.470794 113.032574 0.007675 1.673744 0.593417
Group 544.0 0.099265 0.645221 84.361949 0.069853 0.093750 0.391544 51.584559 0.009191 2.384191 0.303309

Plot da variavel is_canceled em função da variavel customer_type

In [53]:
fig, ax = plt.subplots(figsize=(11, 4))
sns.countplot(x = 'customer_type',hue = 'is_canceled', data = data)
plt.title("cancelamentos por deposit_type")
plt.show()

Plotando os graficos para avaliar as distribuçoes de frequencias e a presencia de outliers¶

In [54]:
# Plotando as distribuçoes de frequencias
cont_data_plot = sel_cont_vars
#data_canc = data[data['is_canceled'] == 1]
fig, axarr = plt.subplots(5, 2, figsize=(10, 30))
for s in range(0, 5):
    sns.histplot(x=cont_data_plot[s], data = data, ax=axarr[s,0])
for s in range(5, len(cont_data_plot)):
    sns.histplot(x=cont_data_plot[s], data = data, ax=axarr[s-5,1])
In [55]:
# Plotando os boxplot
cont_data_plot = sel_cont_vars
#data_canc = data[data['is_canceled'] == 1]
fig, axarr = plt.subplots(5, 2, figsize=(10, 30))
for s in range(0, 5):
    sns.boxplot(y=cont_data_plot[s], x='is_canceled', hue='is_canceled', data = data, ax=axarr[s,0])
for s in range(5, len(cont_data_plot)):
    sns.boxplot(y=cont_data_plot[s], x='is_canceled', hue='is_canceled', data = data, ax=axarr[s-5,1])

As variaveis com maior diversificação das frequencias são com certeza: 'adr', 'lead_time' e 'days_in_waiting_list'. Ninhumas das variaveis parece assumir normalidade. Nas 3 principais distribuçoes nomeadas acima se observam a presencia de outliers.

Finalizando o set de dados finais¶

A criação de sub-variaveis è feita para segmentar cada variavel categorica selecionada para que podemos ter maior escolha na seleção das variaveis mais importante pelo modelo.

In [56]:
#Criando as variaveis dummies pelas variaveis categoricas
#escluindo "meal", "is_repeated_guest", "reservation_status", "reserved_type_room" e "assigned_type_room"
cat_vars=['hotel','arrival_date_month','market_segment',
          'distribution_channel',
          'deposit_type','customer_type'
         ]
for var in cat_vars:
    cat_list='var'+'_'+var
    cat_list = pd.get_dummies(data[var], prefix=var, dtype=int)
    data1=data.join(cat_list)
    data=data1
cat_vars=['hotel','arrival_date_month','market_segment',
          'distribution_channel',
          'deposit_type','customer_type'
         ]
data_vars=data.columns.values.tolist()
to_keep=[i for i in data_vars if i not in cat_vars]
In [57]:
data_final=data[to_keep]
In [58]:
#Eliminando as variaveis indeseijadas
data_final = data_final.drop(columns=[ #continuous
                                      'reservation_status','reservation_status_date',
                                      'stays_in_week_nights','arrival_date_day_of_month','arrival_date_year',
                                      'reservation_status_date','previous_bookings_not_canceled',
                                      'stays_in_weekend_nights','arrival_date_week_number',
                                      'meal','is_repeated_guest',#categorical
                                      'reserved_room_type','assigned_room_type',
                                      'stays_in_weekend_nights','arrival_date_week_number', #post-rfe
                                      ]) 
print(len(data_final. axes[1]))

#Acrescentando as variaveis dummies categoricas country e agent
data_final = data_final.drop(columns=['country', 'agent'])
data_final = pd.concat([data_final, filtered_data_cou, filtered_data_agn], axis=1) #, filtered_data_cou, filtered_data_agn
print(len(data_final. axes[1]))

#Eliminando as variaveis undefined com meno de 10 dados
data_final = data_final.drop(columns=['market_segment_Undefined',
                                      'distribution_channel_Undefined',
                                     ])
print(len(data_final. axes[1]))
data_final.columns.values
47
65
63
Out[58]:
array(['is_canceled', 'lead_time', 'adults', 'children', 'babies',
       'previous_cancellations', 'booking_changes',
       'days_in_waiting_list', 'adr', 'required_car_parking_spaces',
       'total_of_special_requests', 'hotel_City Hotel',
       'hotel_Resort Hotel', 'arrival_date_month_April',
       'arrival_date_month_August', 'arrival_date_month_December',
       'arrival_date_month_February', 'arrival_date_month_January',
       'arrival_date_month_July', 'arrival_date_month_June',
       'arrival_date_month_March', 'arrival_date_month_May',
       'arrival_date_month_November', 'arrival_date_month_October',
       'arrival_date_month_September', 'market_segment_Aviation',
       'market_segment_Complementary', 'market_segment_Corporate',
       'market_segment_Direct', 'market_segment_Groups',
       'market_segment_Offline TA/TO', 'market_segment_Online TA',
       'distribution_channel_Corporate', 'distribution_channel_Direct',
       'distribution_channel_GDS', 'distribution_channel_TA/TO',
       'deposit_type_No Deposit', 'deposit_type_Non Refund',
       'deposit_type_Refundable', 'customer_type_Contract',
       'customer_type_Group', 'customer_type_Transient',
       'customer_type_Transient-Party', 'country_BRA', 'country_PRT',
       'country_ITA', 'country_ESP', 'country_IRL', 'country_BEL',
       'country_FRA', 'country_DEU', 'country_GBR', 'country_NLD',
       'agent_9.0', 'agent_240.0', 'agent_1.0', 'agent_8.0',
       'agent_250.0', 'agent_14.0', 'agent_241.0', 'agent_7.0',
       'agent_not_defined', 'agent_28.0'], dtype=object)

Foram escluidas as variaveis categoricas "meal", "reservation_status", "reserved_type_room" e "assigned_type_room" e "is_repeated_guest".

Provavelmente esiste correlação entre as variaveis "is_repeated_guest" e 'previous_bookings_not_canceled' assim como indicava levemente a heatmap.
A escolha de escluir "meal" foi devida ao acreditar que o tipo de refeiçoes reservadas podem não influir no nosso modelo, assim igualmente como as variaveis "reservation_status", "reserved_type_room" e "assigned_type_room".

Rebalanceando o banco de dados¶

Nesse passo usaremos o algoritmo SMOTE (Synthetic Minority Oversampling Technique).

  1. Funciona ao criar amostragens sintéticas para classe que possui menor frequência.
  2. Escolhe randomicamente os k vizinhos próximos (KNN) para criar amostragens similares.

O rebalanceamento dos dados da variavel dependente foi devido ao desequilíbrio dos dados entre 0 e 1: percentual de pessoas que não cancelaram 72.51 %; percentual de pessoas que cancelaram 27.49 %.
Foi necessario para evitar de ter desequilibrio entre os erros das pessoes que não cancelaram e os acertos das pessoas que cancelaram.

In [59]:
X = data_final.loc[:, data_final.columns != 'is_canceled']
y = data_final.loc[:, data_final.columns == 'is_canceled']

from imblearn.over_sampling import SMOTE

#Dividindo os dados entre test e treino
os = SMOTE(random_state=0)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0)
columns = X_train.columns
os_data_X,os_data_y = os.fit_resample(X_train, y_train)
os_data_X = pd.DataFrame(data=os_data_X,columns=columns )
os_data_y = pd.DataFrame(data=os_data_y,columns=['is_canceled'])

#Mostrando os dados do rebalanceamento em percentuais
print("Tamanho do oversampled é: ",len(os_data_X))
print("Número de não cancelamento:",len(os_data_y[os_data_y['is_canceled']==0]))
print("Número de cancelamento:",len(os_data_y[os_data_y['is_canceled']==1]))
print("Proporção de não cancelamento: ",len(os_data_y[os_data_y['is_canceled']==0])/len(os_data_X)*100, "%")
print("Proporção de cancelamento:",
len(os_data_y[os_data_y['is_canceled']==1])/len(os_data_X)*100, "%")
print(len(data_final))
Tamanho do oversampled é:  88870
Número de não cancelamento: 44435
Número de cancelamento: 44435
Proporção de não cancelamento:  50.0 %
Proporção de cancelamento: 50.0 %
87396
In [60]:
#Plotando os dados rebalanceados
plt.figure(figsize=(15,8))
figure = sns.countplot(x='is_canceled', data = os_data_y)

Seleção de Variáveis via LOGIT¶

Recursive Feature Elimination (RFE) é baseada na ideia de repetidamente construir um modelo e selecionar as variáveis que apresentam as melhores e as piores performance.
O processo é repetido recursivamente até que sejam selecionadas as variáveis que mais importam para o modelo, de modo a ficar com um número parcimonioso de variáveis.

In [61]:
import warnings

#suppress warnings
warnings.filterwarnings('ignore')
In [62]:
#posiçoes das features escluidas

data_final_vars=data_final.columns.values.tolist()
y=['is_canceled']
X=[i for i in data_final_vars if i not in y]

from sklearn.feature_selection import RFE
from sklearn.linear_model import LogisticRegression

rfe = RFE(estimator = LogisticRegression(solver='lbfgs', max_iter=60), n_features_to_select = 55) #, max_features_to_select = 63
rfe = rfe.fit(os_data_X, os_data_y.values.ravel())
print(rfe.support_)
print(rfe.ranking_)
[False  True  True False  True  True False False  True  True  True  True
  True  True  True  True  True  True  True  True  True  True  True  True
  True False  True  True  True  True  True  True  True  True  True  True
  True False  True  True  True  True  True  True  True  True  True  True
  True  True  True  True  True  True  True False  True  True  True  True
  True  True]
[4 1 1 3 1 1 7 5 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 8 1 1 1 1 1 1 1 1 1 1 1
 6 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1]
In [63]:
#Features escluidas

rfe.ranking = rfe.ranking_.tolist()
cols_v = []
cols_excl = []
for i in rfe.ranking_:
    if i > 1:
        data_final.columns.values[i]
        cols_v.append(i)
        
for c in cols_v :
    v = rfe.ranking.index(c)
    cols_excl.append(data_final.columns.values[v])
               
print(cols_excl)
['is_canceled', 'children', 'booking_changes', 'days_in_waiting_list', 'market_segment_Aviation', 'deposit_type_Non Refund', 'agent_1.0']
In [64]:
#Organização das columnas com as variaveis escluidas pelo RFE

ext_in = data_final.columns.tolist().index('is_canceled')
ext = [data_final.columns.values[ext_in]]
cols = list(set(data_final.columns.values) - set(cols_excl) - set(ext))#
X=os_data_X[cols]
y=os_data_y['is_canceled']

O RFE foi configurado a rodar com 55 features num total de 63, escluindo de fato 7 features (as quais acima) mais a variavel dependente que em qualquer caso não deve ser incluida.
O algoritmo foi rodado varias vezes, pois foi utilizado um abordagem de tipo "passo a passo" no acrescentar e diminuir as variaveis umas por vez para que se pudesse encotrar um modelo com o melhor desepenho entre metricas e acuracia.

Implementando o LOGIT¶

A função logit, toma uma probabilidade p como entrada e gera as log-odds dessa probabilidade.
Matematicamente, a função logit é definida como:

$logit(p) = log(p / (1 - p))$

p representa a probabilidade de ocorrência de um evento e logit(p) representa as probabilidades logarítmicas desse evento ocorrer.
As probabilidades logarítmicas podem assumir qualquer valor real de infinito negativo a infinito positivo.

In [65]:
import statsmodels.api as sm

logit_model=sm.Logit(y,X)
result=logit_model.fit(maxiter=100) #maxiter=100
print(result.summary2())
Optimization terminated successfully.
         Current function value: 0.357781
         Iterations 39
                                                                                                                                                    Results: Logit
=======================================================================================================================================================================================================================================================================================================================
Model:                                                                                                Logit                                                                                              Method:                                                                                             MLE       
Dependent Variable:                                                                                   is_canceled                                                                                        Pseudo R-squared:                                                                                   0.484     
Date:                                                                                                 2023-07-14 22:38                                                                                   AIC:                                                                                                63703.9341
No. Observations:                                                                                     88870                                                                                              BIC:                                                                                                64230.0501
Df Model:                                                                                             55                                                                                                 Log-Likelihood:                                                                                     -31796.   
Df Residuals:                                                                                         88814                                                                                              LL-Null:                                                                                            -61600.   
Converged:                                                                                            1.0000                                                                                             LLR p-value:                                                                                        0.0000    
No. Iterations:                                                                                       39.0000                                                                                            Scale:                                                                                              1.0000    
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
                                 Coef.                                         Std.Err.                                         z     P>|z|                                          [0.025                                                                               0.975]                                       
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
country_GBR                      -1.1200                                                                              0.0397 -28.1852 0.0000                                                                               -1.1979                                                                              -1.0422
country_DEU                      -1.4155                                                                              0.0499 -28.3624 0.0000                                                                               -1.5134                                                                              -1.3177
country_FRA                      -1.2138                                                                              0.0411 -29.5130 0.0000                                                                               -1.2944                                                                              -1.1332
arrival_date_month_July          -4.0904                                                                              0.0653 -62.6727 0.0000                                                                               -4.2183                                                                              -3.9624
lead_time                         0.0085                                                                              0.0001  60.5808 0.0000                                                                                0.0082                                                                               0.0088
hotel_Resort Hotel               -0.6255                                                                              0.0698  -8.9606 0.0000                                                                               -0.7624                                                                              -0.4887
customer_type_Transient           1.4612                                                                              0.0748  19.5416 0.0000                                                                                1.3146                                                                               1.6077
country_ITA                      -0.6961                                                                              0.0576 -12.0804 0.0000                                                                               -0.8090                                                                              -0.5831
previous_cancellations            0.5707                                                                              0.0472  12.1013 0.0000                                                                                0.4783                                                                               0.6632
distribution_channel_TA/TO        2.1083                                                                              0.1110  18.9938 0.0000                                                                                1.8907                                                                               2.3259
customer_type_Group              -0.1183                                                                              0.2327  -0.5085 0.6111                                                                               -0.5744                                                                               0.3378
adr                               0.0085                                                                              0.0003  33.3190 0.0000                                                                                0.0080                                                                               0.0090
deposit_type_No Deposit           0.9845                                                                              0.0666  14.7889 0.0000                                                                                0.8540                                                                               1.1149
hotel_City Hotel                 -0.3298                                                                              0.0674  -4.8929 0.0000                                                                               -0.4620                                                                              -0.1977
deposit_type_Refundable           1.2012                                                                              0.3670   3.2733 0.0011                                                                                0.4820                                                                               1.9205
market_segment_Corporate         -2.0057                                                                              0.1250 -16.0405 0.0000                                                                               -2.2507                                                                              -1.7606
agent_28.0                       -1.0886                                                                              0.1397  -7.7900 0.0000                                                                               -1.3625                                                                              -0.8147
agent_9.0                         0.6604                                                                              0.0469  14.0710 0.0000                                                                                0.5684                                                                               0.7524
distribution_channel_GDS          0.9327                                                                              0.2907   3.2084 0.0013                                                                                0.3629                                                                               1.5025
arrival_date_month_January       -3.4772                                                                              0.0731 -47.5417 0.0000                                                                               -3.6205                                                                              -3.3338
arrival_date_month_May           -3.7405                                                                              0.0663 -56.3782 0.0000                                                                               -3.8705                                                                              -3.6104
country_IRL                      -1.1205                                                                              0.0638 -17.5511 0.0000                                                                               -1.2456                                                                              -0.9954
country_NLD                      -1.4457                                                                              0.0825 -17.5297 0.0000                                                                               -1.6073                                                                              -1.2840
arrival_date_month_February      -3.3598                                                                              0.0692 -48.5854 0.0000                                                                               -3.4953                                                                              -3.2242
arrival_date_month_September     -3.9561                                                                              0.0699 -56.5568 0.0000                                                                               -4.0932                                                                              -3.8190
market_segment_Groups            -1.9070                                                                              0.1100 -17.3361 0.0000                                                                               -2.1226                                                                              -1.6914
babies                           -0.2576                                                                              0.1103  -2.3343 0.0196                                                                               -0.4738                                                                              -0.0413
agent_8.0                        -0.7461                                                                              0.0933  -7.9934 0.0000                                                                               -0.9290                                                                              -0.5631
market_segment_Complementary     -2.1985                                                                              0.1788 -12.2992 0.0000                                                                               -2.5488                                                                              -1.8482
agent_14.0                       -0.0898                                                                              0.1063  -0.8443 0.3985                                                                               -0.2981                                                                               0.1186
arrival_date_month_June          -3.9545                                                                              0.0673 -58.7784 0.0000                                                                               -4.0863                                                                              -3.8226
arrival_date_month_March         -3.5691                                                                              0.0672 -53.1087 0.0000                                                                               -3.7008                                                                              -3.4374
market_segment_Direct            -2.7796                                                                              0.1416 -19.6255 0.0000                                                                               -3.0572                                                                              -2.5021
customer_type_Transient-Party    -0.0566                                                                              0.0856  -0.6609 0.5087                                                                               -0.2244                                                                               0.1112
country_ESP                      -0.5088                                                                              0.0433 -11.7553 0.0000                                                                               -0.5936                                                                              -0.4240
required_car_parking_spaces    -367.9017 977324606347738763562705594319887271099121525854230172505446913461384483176448.0000  -0.0000 1.0000 -1915521029646353720571138431757643517946049960555783521165628600361325655228416.0000 1915521029646353720571138431757643517946049960555783521165628600361325655228416.0000
customer_type_Contract            0.5400                                                                              0.1017   5.3121 0.0000                                                                                0.3408                                                                               0.7392
arrival_date_month_December      -3.4925                                                                              0.0722 -48.4010 0.0000                                                                               -3.6340                                                                              -3.3511
arrival_date_month_August        -4.0027                                                                              0.0654 -61.1776 0.0000                                                                               -4.1309                                                                              -3.8744
arrival_date_month_April         -3.5240                                                                              0.0662 -53.2332 0.0000                                                                               -3.6537                                                                              -3.3942
agent_240.0                       0.7278                                                                              0.0527  13.8082 0.0000                                                                                0.6245                                                                               0.8311
total_of_special_requests        -0.8967                                                                              0.0146 -61.5147 0.0000                                                                               -0.9253                                                                              -0.8682
distribution_channel_Direct       1.7378                                                                              0.1461  11.8973 0.0000                                                                                1.4515                                                                               2.0241
arrival_date_month_November      -3.5148                                                                              0.0729 -48.2224 0.0000                                                                               -3.6576                                                                              -3.3719
agent_7.0                        -1.4716                                                                              0.0799 -18.4088 0.0000                                                                               -1.6283                                                                              -1.3150
country_BRA                      -0.4051                                                                              0.0710  -5.7038 0.0000                                                                               -0.5442                                                                              -0.2659
market_segment_Online TA         -1.9400                                                                              0.1073 -18.0882 0.0000                                                                               -2.1502                                                                              -1.7298
country_PRT                       1.0546                                                                              0.0294  35.8161 0.0000                                                                                0.9969                                                                               1.1123
arrival_date_month_October       -3.6188                                                                              0.0691 -52.3804 0.0000                                                                               -3.7542                                                                              -3.4834
market_segment_Offline TA/TO     -3.2224                                                                              0.1062 -30.3402 0.0000                                                                               -3.4306                                                                              -3.0143
agent_241.0                      -0.9863                                                                              0.1067  -9.2439 0.0000                                                                               -1.1954                                                                              -0.7772
agent_250.0                      -0.0477                                                                              0.1112  -0.4295 0.6676                                                                               -0.2656                                                                               0.1701
adults                            0.1811                                                                              0.0211   8.5833 0.0000                                                                                0.1398                                                                               0.2225
country_BEL                      -1.4861                                                                              0.0798 -18.6174 0.0000                                                                               -1.6425                                                                              -1.3296
agent_not_defined                -0.4970                                                                              0.0720  -6.8981 0.0000                                                                               -0.6382                                                                              -0.3558
distribution_channel_Corporate    1.0564                                                                              0.1351   7.8187 0.0000                                                                                0.7916                                                                               1.3212
=======================================================================================================================================================================================================================================================================================================================

Avaliação dos resultados do LOGIT¶

O valor p associado ao teste LLR é usado para determinar a significância estatística da melhoria no ajuste do modelo ao adicionar os preditores adicionalis.
O valor encontrado do Log-Likelihood è -31796.

Se supoe que a hipótese nula $H_0$ assume que os preditores adicionais não melhoram significativamente o ajuste do modelo.
Se o valor p < 0,05, a melhoria no ajuste é considerada estatisticamente significativa.
No nosso caso, se pode rejeitar a hipótese nula pois o nosso LLR p-value = 0.

O valor da "convergencia" è indicada com 0 ou 1. Dos resultados obtidos se observa que Converged = 1, o que significa que o algoritmo atingiu a convergencia em um ponto em que é improvável que novas iterações melhorem significativamente a solução. No nosso caso foi configurado um numero maximo de iteraçoes = 100, mas so 39 tentativas foram suficientes.
O algoritmo atingiu uma solução estável, ou seja visa encontrar o melhor conjunto de coeficientes que minimizaram a diferença entre as probabilidades previstas e os resultados binários reais do conjunto de dados.

O Pseudo R-squared, consiste na conta do número de previsões corretas e a comparação com o número total de observações. Como o regressando do modelo logit assume valores 0 ou 1, se a probabilidade prevista for maior que 0.5, será classificada como 1, caso contrário, será classificada como 0.
No nosso caso o resultado encontrado foi 0,484, geralmente maior è o valor, maior è a verossimilhança do modelo.

In [66]:
#Calculando as log_odds

log_odds = result.params.values
pd.DataFrame(log_odds, 
             X.columns, 
             columns=['coef'])\
            .sort_values(by='coef', ascending=False)
Out[66]:
coef
distribution_channel_TA/TO 2.108303
distribution_channel_Direct 1.737810
customer_type_Transient 1.461186
deposit_type_Refundable 1.201217
distribution_channel_Corporate 1.056374
country_PRT 1.054616
deposit_type_No Deposit 0.984461
distribution_channel_GDS 0.932744
agent_240.0 0.727776
agent_9.0 0.660375
previous_cancellations 0.570742
customer_type_Contract 0.540004
adults 0.181112
adr 0.008547
lead_time 0.008486
agent_250.0 -0.047742
customer_type_Transient-Party -0.056588
agent_14.0 -0.089750
customer_type_Group -0.118329
babies -0.257550
hotel_City Hotel -0.329839
country_BRA -0.405051
agent_not_defined -0.496955
country_ESP -0.508805
hotel_Resort Hotel -0.625543
country_ITA -0.696068
agent_8.0 -0.746080
total_of_special_requests -0.896737
agent_241.0 -0.986266
agent_28.0 -1.088611
country_GBR -1.120048
country_IRL -1.120486
country_FRA -1.213825
country_DEU -1.415545
country_NLD -1.445669
agent_7.0 -1.471639
country_BEL -1.486062
market_segment_Groups -1.907027
market_segment_Online TA -1.939999
market_segment_Corporate -2.005674
market_segment_Complementary -2.198501
market_segment_Direct -2.779650
market_segment_Offline TA/TO -3.222448
arrival_date_month_February -3.359753
arrival_date_month_January -3.477187
arrival_date_month_December -3.492538
arrival_date_month_November -3.514769
arrival_date_month_April -3.523991
arrival_date_month_March -3.569124
arrival_date_month_October -3.618819
arrival_date_month_May -3.740465
arrival_date_month_June -3.954469
arrival_date_month_September -3.956093
arrival_date_month_August -4.002656
arrival_date_month_July -4.090368
required_car_parking_spaces -367.901652
In [67]:
#Mostrando os coeficientes de adjuste

odds = np.exp(result.params.values)
pd.DataFrame(odds, 
             X.columns, 
             columns=['coef'])\
            .sort_values(by='coef', ascending=False)
Out[67]:
coef
distribution_channel_TA/TO 8.234254e+00
distribution_channel_Direct 5.684882e+00
customer_type_Transient 4.311067e+00
deposit_type_Refundable 3.324160e+00
distribution_channel_Corporate 2.875924e+00
country_PRT 2.870872e+00
deposit_type_No Deposit 2.676369e+00
distribution_channel_GDS 2.541473e+00
agent_240.0 2.070471e+00
agent_9.0 1.935518e+00
previous_cancellations 1.769579e+00
customer_type_Contract 1.716013e+00
adults 1.198550e+00
adr 1.008583e+00
lead_time 1.008522e+00
agent_250.0 9.533800e-01
customer_type_Transient-Party 9.449833e-01
agent_14.0 9.141595e-01
customer_type_Group 8.884034e-01
babies 7.729429e-01
hotel_City Hotel 7.190398e-01
country_BRA 6.669427e-01
agent_not_defined 6.083802e-01
country_ESP 6.012139e-01
hotel_Resort Hotel 5.349707e-01
country_ITA 4.985415e-01
agent_8.0 4.742221e-01
total_of_special_requests 4.078986e-01
agent_241.0 3.729666e-01
agent_28.0 3.366839e-01
country_GBR 3.262642e-01
country_IRL 3.261211e-01
country_FRA 2.970589e-01
country_DEU 2.427932e-01
country_NLD 2.355884e-01
agent_7.0 2.295489e-01
country_BEL 2.262619e-01
market_segment_Groups 1.485213e-01
market_segment_Online TA 1.437041e-01
market_segment_Corporate 1.345695e-01
market_segment_Complementary 1.109693e-01
market_segment_Direct 6.206024e-02
market_segment_Offline TA/TO 3.985737e-02
arrival_date_month_February 3.474382e-02
arrival_date_month_January 3.089418e-02
arrival_date_month_December 3.042356e-02
arrival_date_month_November 2.975467e-02
arrival_date_month_April 2.948153e-02
arrival_date_month_March 2.818052e-02
arrival_date_month_October 2.681432e-02
arrival_date_month_May 2.374306e-02
arrival_date_month_June 1.916884e-02
arrival_date_month_September 1.913774e-02
arrival_date_month_August 1.826705e-02
arrival_date_month_July 1.673308e-02
required_car_parking_spaces 1.668563e-160
In [68]:
#Plotando as variaveis com base na importancia
coefficients = pd.DataFrame(odds, X.columns, columns=['coef']).sort_values(by='coef', ascending=True)

plt.figure(figsize=(10,8))
plt.barh(coefficients.index, coefficients['coef'], color=['C{}'.format(i) for i in range(len(coefficients))])
plt.xlabel('Probabilidade (%)')
plt.ylabel('Variáveis')
plt.title('Gráfico de Importância')
plt.show()

Conforme os resultados do grafico de importancia, as sub-variaveis que pertecem ao canal de distribução "distribution_channel" tem uma importancia muito alta pois as 4 sub-variaveis estão nas primeiras 7 colocaçoes.
Nos primeiros 2 lugares se colocam a distribução por meio de tour operator ou tour agency, e por meio de distribução direta.

Tambem o tipo de cliente "customer_type" no especifico "Transient" tem muita importancia;

Faz sentido tambem que o tipo de pagamento tenha influência nos cancelamentos, de fato è posssivel observar que, os pagamentos com rembolsos e os pagamentos sem adiantamento são aqueles com mais probabilidade de cancelamento.

O pais de origem tambem tem muita importancia pois tem um destaque muito forte entre clientes de Portugal comparados com os outros paises.

Os agentes tambem tem uma probabilidade muito variada entre as 10 sub-variaveis. Os agentes com id 240 e 9 são aqueles que mais se destacam dos outros.

E' importante evidenciar como todos os 12 meses de reserva são menos importantes em termos de probabilidade.

Tambem as variaveis "previous_cancellations", "adr", "lead_time", que representam quem ja tinha cancelamentos antecedentes, o custo diario e o tempo de espera entre a reserva e o dia de chegada, tambem tem boa parte da probabilidade.

Avaliação da Previsibilidade do Modelo¶

In [69]:
#Dividindo os dados entre treino e test

from sklearn.linear_model import LogisticRegression
from sklearn import metrics
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=0)
logreg = LogisticRegression(max_iter=2000)
logreg.fit(X_train, y_train)
Out[69]:
LogisticRegression(max_iter=2000)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
LogisticRegression(max_iter=2000)
In [70]:
y_pred = logreg.predict(X_test)
print('Acurácia do Modelo na Base de Teste: {:.2f}'.format(logreg.score(X_test, y_test)))
Acurácia do Modelo na Base de Teste: 0.84

Foi dividida a base de dados entre dados para uso de treino e dados para uso de test, em percentuais de 70% e 30%. O resultado foi que o modelo, fora da amostra, tem acuracia do 84% o que è relativamente bom.

A Matriz de confusão gera os Verdadeiro positivos VP, os Falsos positivos FP e os Falsos negativos FN para calcular as metricas para avaliar a acuracia.

In [71]:
#Gerando a matriz de confusão

from sklearn.metrics import confusion_matrix
confusion_matrix = confusion_matrix(y_test, y_pred)
print(confusion_matrix)

print("Esse modelo nos dá uma relação de", 
      str(confusion_matrix[0,0]), "+", str(confusion_matrix[1,1]), 
      "=", confusion_matrix[0,0] + confusion_matrix[1,1], "predições corretas e", 
      str(confusion_matrix[0,1]), "+", str(confusion_matrix[1,0]), 
      "=", confusion_matrix[0,1] + confusion_matrix[1,0], "previções erradas")
[[11596  1655]
 [ 2626 10784]]
Esse modelo nos dá uma relação de 11596 + 10784 = 22380 predições corretas e 1655 + 2626 = 4281 previções erradas

VP = 11603
FP = 2634
FN = 10776

Os verdadeiros positivos são os que o modelo afirmou que o resultado era 0 e o resultado real na base de testes era 0 e quando o modelo dizia que era 1 o resultado real da base de testes confirmava o número 1. Os Falsos positivos, são o inverso do explicado anteriormente. Ou seja, o modelo diz que é 1 mas o resultado real diz que é zero.

Usaremos as metricas Precision, Recall e F1-score para ter mais informaçoes sobre a acuracia.

In [72]:
# Calculando as Metricas

from sklearn.metrics import classification_report
print(classification_report(y_test, y_pred))
              precision    recall  f1-score   support

           0       0.82      0.88      0.84     13251
           1       0.87      0.80      0.83     13410

    accuracy                           0.84     26661
   macro avg       0.84      0.84      0.84     26661
weighted avg       0.84      0.84      0.84     26661

\begin{align*} Precision = \frac{VP}{(VP+FP)} ; \hspace{1cm} Recall = \frac{VP}{(VP+FN)} ; \hspace{1cm} F1-score = média\hspace{0.2cm} harmônica\hspace{0.2cm} do\hspace{0.2cm} Precision\hspace{0.2cm} e\hspace{0.2cm} do \hspace{0.2cm}Recall \hspace{1cm} \end{align*}

.

A Curva ROC è uma metrica visual e analisa a área de uma curva que relaciona a taxa de verdadeiros positivos com falsos positivos. A area entre a linea vermelha e a curva azul (curva da regressao) è a area de acuracia do modelo.

In [73]:
# Plotando a Curva ROC

from sklearn.metrics import roc_auc_score
from sklearn.metrics import roc_curve
logit_roc_auc = roc_auc_score(y_test, logreg.predict(X_test))
fpr, tpr, thresholds = roc_curve(y_test, logreg.predict_proba(X_test)[:,1])
plt.figure()
plt.figure(figsize=(15,8))
plt.plot(fpr, tpr, label='Regressão Logística (área = %0.2f)' % logit_roc_auc)
plt.plot([0, 1], [0, 1],'r--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('Taxa de Falso Positivo')
plt.ylabel('Taxa de Verdadeiro Positivo')
plt.title('CURVA ROC')
plt.legend(loc="lower right")
plt.savefig('Log_ROC')
plt.show()
<Figure size 640x480 with 0 Axes>

Conclusões finais¶

Com base nos resultados da análise de regressão logística, podemos concluir que a variavel dependente "is_canceled" têm uma influência significativa na probabilidade de realizar um cancelamento. O modelo apresentou uma acurácia de 84%, demonstrando sua capacidade relativamente boa de prever os cancelamentos em uma amostra de dados de teste.

Examinando os coeficientes das variáveis independentes, observamos que o canal de distribução, o tipo de pagamento, o tipo de cliente, o pais de proveniencia, os agentes, valor da diaria, tempo da reserva e cancelamentos antecedentes, demonstraram ser os preditores mais relevantes. Clientes que compraram por meio de todos os canais de distribução sobretudo por meio de agencias e operadores turisticos, clientes que usaram pagamentos não adiantados ou com rembolso, clientes com proveniencia de Portugal apresentaram maior probabilidade de cancelamento.

Além disso, observamos um impacto muito baixo entre a probabilidade de cancelamento, a temporada da reserva e os outros paises europeus excluindo Portugal.

É importante ressaltar que esse modelo de regressão logística tem suas limitações enquanto não foram rodados testes de lineariade nem tecnicas focadas na eliminação ou redução dos outliers, portanto a acuracia do modelo è melhoravel apesar do resultado bom.

Com base nos resultados desta análise, as agencias de turismo e os operadores turisticos, assim como os agentes, podem utilizar essas informações para aprimorar suas estratégias para reter clientes e evitar cancelamentos. Poderiam ser utilizados descontos especiais para clientes portugueses e oferecer vantagens para clientes que tentaram cancelar no passado.

In [ ]: